#include "verilated_vcd_c.h"
#include "Vtop___024root.h"
#include "Vtop.h"
#include "Vtop__Dpi.h"
#include "verilated.h"
#include "svdpi.h"
// #include <bits/types/struct_timeval.h>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <sys/time.h>

// #define VCD 1

#ifdef VCD
VerilatedContext* contextp = NULL;
VerilatedVcdC* tfp = NULL;
#endif

// #define CHISEL 1

static Vtop* top;
extern "C" {
  void step_and_dump_wave();
  void init_sim();
  void step_one(unsigned int inst, int *ebreak, unsigned int *dnpc, unsigned int *gpr);
  void exit_sim();
  void read_gpr(unsigned int *gpr);
  unsigned int vaddr_read(unsigned addr, int len);
  void vaddr_write(unsigned addr, int len, unsigned int data);
  int mem_read(int raddr);
  void mem_write(int waddr, int wdata, char wmask);
}

void step_and_dump_wave() {
  top->eval();
#ifdef VCD
  contextp->timeInc(1);
  tfp->dump(contextp->time());
#endif
}

void init_sim() {
#ifdef VCD
  contextp = new VerilatedContext;
  tfp = new VerilatedVcdC;
#endif
  top = new Vtop;
#ifdef VCD
  contextp->traceEverOn(true);
  top->trace(tfp, 0);
  tfp->open("dump.vcd");
#endif

  // top->clock=0; top->reset=1; top->clk_mem = 0; step_and_dump_wave();
  // top->clk_mem = 1; step_and_dump_wave();
  // top->clock=1; top->clk_mem = 0; step_and_dump_wave();
  top->clock=0; top->reset=1; step_and_dump_wave();
  top->clock=1; step_and_dump_wave();
}

void step_one(unsigned int inst, int *ebreak, unsigned int *dnpc, unsigned int *gpr) {
  char flag = 0;

  // top->clk_mem = 1; step_and_dump_wave();
#ifdef CHISEL
  // top->io_inst = inst;
#else
  // top->inst = inst;
#endif
  top->clock  = 0; top->reset = 0; step_and_dump_wave();

#ifdef CHISEL
  const svScope scope = svGetScopeFromName("TOP.top.idu.decoder.ebreak");
  assert(scope);
  svSetScope(scope);

  IsEbreak(ebreak);
#else
  const svScope scope  = svGetScopeFromName("TOP.top.idu.Decoder");
  // const svScope scope1 = svGetScopeFromName("TOP.top.CSR");
  assert(scope);
  // assert(scope1);
  svSetScope(scope);

#endif

  do {
    // top->clk_mem  = 1; step_and_dump_wave();
    // top->clock  = 1; top->clk_mem  = 0; step_and_dump_wave();
    //
    // top->clk_mem  = 1; step_and_dump_wave();
    // top->clock  = 0; top->clk_mem  = 0; step_and_dump_wave();
    top->clock  = 1; step_and_dump_wave();
    top->clock  = 0; step_and_dump_wave();
  } while (top->done == 0);

  IsEbreak(ebreak);
#ifndef CHISEL
  Flag(&flag);
  assert(flag != 0);
#endif

  // get registers
  read_gpr(gpr);

#ifdef CHISEL
  *dnpc   = top->io_addr;
#else
  *dnpc   = top->addr;
#endif
}

void exit_sim() {
  step_and_dump_wave();
  // tfp->close();
  top->final();
}

void read_gpr(unsigned int *gpr) {
#ifdef CHISEL
    gpr[0] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_0;
    gpr[1] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_1;
    gpr[2] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_2;
    gpr[3] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_3;
    gpr[4] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_4;
    gpr[5] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_5;
    gpr[6] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_6;
    gpr[7] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_7;
    gpr[8] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_8;
    gpr[9] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_9;
    gpr[10] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_10;
    gpr[11] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_11;
    gpr[12] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_12;
    gpr[13] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_13;
    gpr[14] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_14;
    gpr[15] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_15;
    gpr[16] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_16;
    gpr[17] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_17;
    gpr[18] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_18;
    gpr[19] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_19;
    gpr[20] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_20;
    gpr[21] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_21;
    gpr[22] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_22;
    gpr[23] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_23;
    gpr[24] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_24;
    gpr[25] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_25;
    gpr[26] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_26;
    gpr[27] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_27;
    gpr[28] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_28;
    gpr[29] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_29;
    gpr[30] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_30;
    gpr[31] = top->rootp->top__DOT__ifu__DOT__rf__DOT__rf_31;
#else
  for (int i = 0; i < 32; i ++) {
    gpr[i] = top->rootp->top__DOT__ifu__DOT__RegisterFile__DOT__rf[i];
  }
#endif
}

int count = 0;

// get time
static uint64_t boot_time = 0;

static uint64_t get_time_internal() {
  struct timeval now;
  gettimeofday(&now, NULL);
  uint64_t us = now.tv_sec * 1000000 + now.tv_usec;

  return us;
}

uint64_t get_time() {
  if (boot_time == 0) boot_time = get_time_internal();
  uint64_t now = get_time_internal();

  return now - boot_time;
}

int mem_read(int raddr) {
  // printf("Read from %#08x\n", raddr);
  // printf("count %d\n", count++);

  // int data = vaddr_read(raddr & ~0x3u, 4);
  // printf("Reading data:%#08x\n", data);
  
  // CLOCK
  if (raddr == 0xa0000048) {
    uint64_t us = get_time();
    return us;
  }
  if (raddr == 0xa000004c) {
    uint64_t us = get_time();
    return us >> 32;
  }

  // Access memory
  return vaddr_read(raddr & ~0x3u, 4);
}

void mem_write(int waddr, int wdata, char wmask) {
  // serial port
  if (waddr == 0xa00003f8) {
    putchar((char)wdata);
    return;
  }

  int data = vaddr_read(waddr & ~0x3u, 4);

  switch (wmask) {
    case 1:  data = (data & ~0x000000ff) + (wdata & 0x000000ff);         break;
    case 2:  data = (data & ~0x0000ff00) + ((wdata & 0x000000ff) << 8);  break;
    case 3:  data = (data & ~0x0000ffff) + (wdata & 0x0000ffff);         break;
    case 4:  data = (data & ~0x00ff0000) + ((wdata & 0x000000ff) << 16); break;
    case 8:  data = (data & ~0xff000000) + ((wdata & 0x000000ff) << 24); break;
    case 12: data = (data & ~0xffff0000) + ((wdata & 0x0000ffff) << 16); break;
    case 15: data = wdata; break;
  }

  // printf("Wirte %#08x\n", data);

  // Access memory
  vaddr_write(waddr & ~0x3u, 4, data);
}
